Attribute VB_Name = "SharedMath"
'====================================================================================
'SharedMath
'====================================================================================
'NOTE: Please be careful when editing this page. This same module is used both in _
       the game and the level editor
Option Explicit

'Basic co-ordinates. Used in a number of places for X/Y location in the maze, _
 width / height, and vectors
Public Type PM_XY
    X As Long
    Y As Long
End Type

'Direction sprites are travelling:
'Note: These values are the same as the values used for the presence of walls in _
       the level. This means that you can check if a wall exists in the direction _
       you are travelling, without having to check each direction individually
Public Enum PM_DIRECTION
    DIR_UP = 1
    DIR_RIGHT = 2
    DIR_DOWN = 4
    DIR_LEFT = 8
    DIR_ALL = DIR_UP Or DIR_RIGHT Or DIR_DOWN Or DIR_LEFT
End Enum

'What can be in a cell in the maze. When prompted for one, they can also often be _
 combined, such as `PM_WALL_TOP Or PM_WALL_LEFT Or PM_HEART`
Public Enum PM_TILE
    PM_WALL_TOP = 1
    PM_WALL_RIGHT = 2
    PM_WALL_BOTTOM = 4
    PM_WALL_LEFT = 8
    PM_WALL_ALL = PM_WALL_TOP Or PM_WALL_RIGHT Or PM_WALL_BOTTOM Or PM_WALL_LEFT
    PM_COOKIE_TOP = 16
    PM_COOKIE_LEFT = 32
    PM_COOKIE_CENTER = 64
    PM_COOKIE_ALL = PM_COOKIE_TOP Or PM_COOKIE_LEFT Or PM_COOKIE_CENTER
    PM_HEART = 128
End Enum

'====================================================================================
'Range: Keep a number between an upper and lower limit
'====================================================================================
Public Function Range(ByVal Number As Long, Optional LowerLimit As Long = 0, Optional UpperLimit As Long = 255) As Long
    If Number > UpperLimit Then Range = UpperLimit: Exit Function
    If Number < LowerLimit Then Range = LowerLimit Else Range = Number
End Function

'====================================================================================
'GetTileFromPx: Given a precise location on the level, return the nearest tile
'====================================================================================
Public Function GetTileFromPx(ByVal Value As Long) As Long
    'Get the nearest approximation of which tile the sprite is on, _
     (The level is split into 64x64 tiles which make up the maze)
    
    'In VB a "\" is a divide that drops the fraction, _
     Use "/" to divide and keep any fraction on the number. _
     In this case we only want a whole number, so \ is used
    GetTileFromPx = Value \ TileSize
End Function

'====================================================================================
'RemoveBits: Remove parts of a tile value - walls / cookies &c.
'====================================================================================
Public Function RemoveBits(ByVal Value As PM_TILE, ByVal Item As PM_TILE) As Byte
    'Cookies are represented negatively on the level, so that bits 16, 32 & 64 are _
     set to 0 to add the cookies, and 1 when removing - the opposite of walls and _
     everything else.
    If (Item And PM_COOKIE_ALL) > 0 Then
        'Flip the cookie bits over before removing them
        RemoveBits = (Value And Not Item) Or (Item And PM_COOKIE_ALL)
    Else
        'Bitwise AND NOT removes bits
        RemoveBits = Value And Not Item
    End If
End Function

'====================================================================================
'AddBits: Add bits to a tile value, walls / cookies &c.
'====================================================================================
Public Function AddBits(ByVal Value As PM_TILE, ByVal Item As PM_TILE) As Byte
    'As above, because cookies are represented negatively, we flip those bits as _
     we receive 1's from PM_TILE, but want to set 0's
    If (Item And PM_COOKIE_ALL) > 0 Then
        'Bitwise XOR flips bits. `Item And PM_COOKIE_ALL` clips `Item` to just the _
         cookie bits 16, 32 & 64 so that they get flipped and everything else is _
         ignored. There's a lot of bitwise math in this program, you really should _
         look up these commands carefully
        AddBits = Value Or (Item Xor Item And PM_COOKIE_ALL)
    Else
        'Bitwise OR merges bits together
        AddBits = Value Or Item
    End If
End Function

'====================================================================================
'ToggleBits: Add/remove bits on a tile value accordingly to their existing state
'====================================================================================
Public Function ToggleBits(ByVal Value As PM_TILE, ByVal Item As PM_TILE) As Byte
    'Straight forward this one, just flip the specified bits
    ToggleBits = Value Xor Item
End Function

'====================================================================================
'IfBits: If a tile value has something particular on it (wall/cookie &c.)
'====================================================================================
Public Function IfBits(ByVal Value As PM_TILE, ByVal Item As PM_TILE, Optional ByVal Mask As PM_TILE = -1) As Boolean
    'This function takes a byte value representing a tile, and something to look _
     for (the `Item`), for example `PM_WALL_TOP`. It will return True if that is _
     in the tile value, False if not.
     
    'The optional `Mask` parameter allows you to specify a range of bits the `Item` _
    'can be from. For example: _
      'Does the value "26" contain left or right walls? _
      ?IfBits(26, PM_WALL_LEFT Or PM_WALL_RIGHT, PM_WALL_ALL)
    
    'If the optional parameter is not provided, it's the same as `Item`
    If Mask = -1 Then Mask = Item
    'If a test involves cookies, flip those bits as cookies are represented _
     negatively on the level, where 0 is present and 1 = missing
    If (Item And PM_COOKIE_ALL) > 0 Then
        Item = Item Xor (Mask And PM_COOKIE_ALL)
    End If
    'Test and return True / False
    IfBits = ((Value And Mask) = Item)
End Function

'====================================================================================
'IfNotBits: Same as IfBits, but tests if something is not there
'====================================================================================
Public Function IfNotBits(ByVal Value As PM_TILE, ByVal Item As PM_TILE) As Boolean
    IfNotBits = (((Value Xor PM_COOKIE_ALL) And Item) = 0)
End Function

'====================================================================================
'GetPythagDistanceFromPoint: Return the distance in pixels from one point to another
'====================================================================================
Public Function GetPythagDistanceFromPoint(ByVal FromX As Long, ByVal FromY As Long, ByVal ToX As Long, ByVal ToY As Long) As Long
    'This function uses Pytahgoras' Theorem to find the distance from one point to _
     another in pixels, regardless of the direction. This always returns a positive _
     number even if the From or To points are negative
    
    'This function works by imagining a triangle where the line between the two _
     points we specify is the slope of the triangle and the other two lines are _
     straight. See a more detailed explanation at _
       http://www.purplemath.com/modules/distform.htm
    
    GetPythagDistanceFromPoint = _
    Int(Sqr( _
        ((ToX - FromX) ^ 2) + _
        ((ToY - FromY) ^ 2) _
    ))
End Function

'====================================================================================
'GetReverseDirection: Return the opposite direction, given a direction
'====================================================================================
Public Function GetReverseDirection(ByVal CurrentDirection As PM_DIRECTION) As PM_DIRECTION
    If CurrentDirection = 0 Then Exit Function
    'This shortcut treats the number like a clock face and roates it two quarters _
     clockwise `+2) Mod 4`. `Log(...) / Log (2)` converts a number from a power of 2 _
     back to the original exponent. e.g. 2^3 = 8, Log(8)/Log(2) = 3
    GetReverseDirection = 2 ^ ( _
        ((Log(CurrentDirection) / Log(2)) + 2) Mod 4 _
    )
End Function

'====================================================================================
'GetVectorFromDirection: Given a PM_DIRECTION, returns X and Y as -1, 0, +1
'====================================================================================
Public Function GetVectorFromDirection(ByVal Direction As PM_DIRECTION) As PM_XY
    'Create a vector for the Sprite's direction:
    'Left = 8, Right = 2. Use `And 10` to ignore the up/down.
    '8 - 4 = A positive number, 2 - 4 = A negative number.
    '`-Sgn` gives us -1 for moving Left and 1 for moving right.
    If (Direction And 10) > 0 Then GetVectorFromDirection.X = -Sgn((Direction And 10) - 4) Else GetVectorFromDirection.X = 0
    'Up = 1, Down = 4. As before, returns -1 for up and 1 if going down.
    If (Direction And 5) > 0 Then GetVectorFromDirection.Y = Sgn((Direction And 5) - 3) Else GetVectorFromDirection.Y = 0
End Function

'====================================================================================
'GetDirectionFromVector: Given a vector, returns a PM_DIRECTION
'====================================================================================
Public Function GetDirectionFromVector(ByRef Vector As PM_XY) As PM_DIRECTION
    If Sgn(Vector.X) <> 0 Then GetDirectionFromVector = 2 ^ (-Sgn(Vector.X) + 2): Exit Function
    If Sgn(Vector.Y) <> 0 Then GetDirectionFromVector = 2 ^ (1 + Sgn(Vector.Y))
End Function

'=== END ============================================================================
'This work is licenced under the Creative Commons Attribution 2.5 License
